#include <slothttpd/stage/send_file.hpp>
#include <slothttpd/stage/send_headers.hpp>
#include <slothttpd/connection.hpp>
#include <slothttpd/http/header/responce.hpp>
#include <slothttpd/http/status.hpp>
#include <slothttpd/logger.hpp>

#include <cerrno>

#include <sys/sendfile.h>
#include <sys/types.h>

#include <boost/bind.hpp>

namespace slothttpd {
namespace stage {

send_file::send_file(connection_ptr conn, const char *path, config::file_location &conf) : 
    ref_counted(conn),
    offset_(0),
    header_sender_(nullptr)
{
    (void) conf.send_file_cache_fd;
    
    header_sender_ = c->pool.construct<send_headers>(conn, path, conf,
                                                     boost::bind(&send_file::on_headers_sent, send_file_ptr(this)));
}

void send_file::on_headers_sent(send_file_ptr s)
{
    if (s->c->request.method_ == http::method::get) {
        s->responce_file = s->header_sender_->responce_file;
        s->header_sender_ = nullptr;
        s->post_send_wait();
    }
}

void send_file::post_send_wait()
{
    c->socket.async_send(boost::asio::null_buffers(), 
                         boost::bind(&send_file::handle_send,
                                     send_file_ptr(this),
                                     boost::asio::placeholders::error));
}

void send_file::handle_send(send_file_ptr r, const boost::system::error_code &err)
{
    try {
        if (!err) {
            int socket = r->c->socket.native_handle();
            int fd = r->responce_file->descriptor();
            
            for (;;) {
                // TODO: use kernel socket output buffer size
                ssize_t cb = ::sendfile(socket, fd, &r->offset_, 64 * 1024);
                
                if (cb < 0) {
                    if (errno == EAGAIN) {
                        r->post_send_wait();
                    } else if (errno != EPIPE && errno != ECONNRESET){
                        logger() << "sendfile: " << strerror(errno) << std::endl;
                    }
                } else if (r->offset_ < r->responce_file->size()) {
                    continue;
                }
                break;
            }
        }
    } catch (const std::exception &ex) {
        logger() << ex.what() << std::endl;
        r->c->close();
    }
}

}
}
